﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.IO;

namespace ExchangeHog.MessageParser
{
	//	
	//	parameter := attribute "=" value
	//	
	//	attribute := token
	//	             ; Matching of attributes
	//	             ; is ALWAYS case-insensitive.
	//	
	//	value := token / quoted-string
	//	
	//	token := 1*<any (US-ASCII) CHAR except SPACE, CTLs,
	//	            or tspecials>
	//	
	//	tspecials :=  "(" / ")" / "<" / ">" / "@" /
	//	              "," / ";" / ":" / "\" / <">
	//	              "/" / "[" / "]" / "?" / "="
	//	              ; Must be in quoted-string,
	//	              ; to use within parameter values
	//
	//	delimiters  =  specials / linear-white-space / comment
	//
	class ContentTypeParamsToken: BaseToken
	{
		internal enum TokenType { NONE, TOKEN, QUOTEDSTRING, EQUAL, SEMICOLON, SPACE, CRLF, EOF};
		internal TokenType TType { get; private set; }
		public String Repr { get; internal set; }
		internal int TokenTypeAsInt() { return (int)TType; }

		internal ContentTypeParamsToken(TokenType tt = TokenType.NONE, string repr = null) { TType = tt; Repr = repr; }

		internal void MarkAsCRLF() { TType = TokenType.CRLF; }
		internal void MarkAsSPACE() { TType = TokenType.SPACE; }

		internal bool IsSPACE() { return TType == TokenType.SPACE; }
	}

	class ContentTypeParamsLexer : BaseLexer<ContentTypeParamsToken>
	{
		void ExpectLWS()
		{
			Char ch = ReadChar();
			Expect(delegate() { return (ch == ' ' || ch == '\t'); }, ch, "space or tab");
		}

		private void ParseQuotedString()
		{
			Char ch = ReadChar();
			Expect(delegate() { return (ch == '\"'); }, ch, "quote '\"'");

			StringBuilder sb = new StringBuilder();
			bool isEndOfQuotedStringReached = false;
			while (!IsEndOfStream() && !isEndOfQuotedStringReached)
			{
				ch = ReadChar();
				switch (ch)
				{
					case '\"':
						isEndOfQuotedStringReached = true;
						break;
					case '\\':
						ch = ReadChar();
						if (ch == '\r')
						{
							ch = PeekChar();
							if (ch == '\n')
							{
								sb.Append("\n"); // CRLF is quoted - add it to the string's body
								SkipChar();
								ExpectLWS();
							}
						}
						else
							sb.Append(ch);
						break;
					default:
						sb.Append(ch);
						break;
				}
			}
			Tokens.Enqueue(new ContentTypeParamsToken(ContentTypeParamsToken.TokenType.QUOTEDSTRING, sb.ToString()));
		}

		//
		//	tspecials :=  "(" / ")" / "<" / ">" / "@" /
		//	              "," / ";" / ":" / "\" / <">
		//	              "/" / "[" / "]" / "?" / "="
		//
		static bool DoesBelongToToken(Char ch)
		{
			return
				31 < ((short)ch) && ((short)ch) < 127 &&
				ch != ' ' && ch != '\t' &&
				ch != '(' && ch != ')' && ch != '<' && ch != '>' && ch != '@' &&
				ch != ',' && ch != ';' && ch != ':' && ch != '\\' && ch != '\"' &&
				ch != '/' && ch != '[' && ch != ']' && ch !='?' && ch != '=';
		}

		private void ParseToken()
		{
			String repr;

			Char ch = ReadChar();
			Expect(
				delegate()
				{
					return DoesBelongToToken(ch);
				}, ch, "any char except specials, space and ctls");

			StringBuilder sb = new StringBuilder();
			sb.Append(ch);

			if (!IsEndOfStream())
			{
				ch = PeekChar();

				while (!IsEndOfStream() && DoesBelongToToken(ch))
				{
					sb.Append(ch);
					SkipChar();
					if (!IsEndOfStream())
						ch = PeekChar();
				}
			}
			repr = sb.ToString();
			Tokens.Enqueue(new ContentTypeParamsToken(ContentTypeParamsToken.TokenType.TOKEN, repr));
		} // end of ParseToken

		public ContentTypeParamsToken NextToken()
		{
			if (Tokens.Count == 0)
				do
					if (!IsEndOfStream())
					{
						Char ch = PeekChar();
						switch (ch)
						{
							case '=': SkipChar(); Tokens.Enqueue(new ContentTypeParamsToken(ContentTypeParamsToken.TokenType.EQUAL, Char.ToString(ch))); break;
							case ';': SkipChar(); Tokens.Enqueue(new ContentTypeParamsToken(ContentTypeParamsToken.TokenType.SEMICOLON, Char.ToString(ch))); break;
							case '"': ParseQuotedString(); break;
							case '\t':
							case ' ': ParseLWS(); break;
							case '\r': ParseCRLF(); break;
							default:
								if (0 < (short)ch && (short)ch < 127)
									ParseToken(); 
								else
									throw new ApplicationException("Unexpected character '" + ch + "'");
								break;
						}
					}
					else
						Tokens.Enqueue(new ContentTypeParamsToken(ContentTypeParamsToken.TokenType.EOF));
				while (Tokens.Count == 0 && !IsEndOfStream());
			return Tokens.Dequeue();
		} // end of NextToken

		public ContentTypeParamsToken ParseThis(ContentTypeParamsToken.TokenType ttExpected)
		{
			return base.ParseThis(ttExpected);
		}
	} // end of ContentTypeParamsLexer
}
